/**
 * Test fsyncLock functionality
 * - Skip for all storage engines which don't support fsync
 * - Run the fsyncLock command, confirm we lock correctly with currentOp
 * - Confirm that we cannot insert during fsyncLock
 * - Confirm that writes can progress after fsyncUnlock
 * - Confirm that the command can be run repeatedly without breaking things
 * - Confirm that the pseudo commands and eval can perform fsyncLock/Unlock
 */
(function() {
"use strict";

// Start with a clean DB
var fsyncLockDB = db.getSisterDB('fsyncLockTestDB');
fsyncLockDB.dropDatabase();

// Tests the db.fsyncLock/fsyncUnlock features
var storageEngine = db.serverStatus().storageEngine.name;

// As of SERVER-18899 fsyncLock/fsyncUnlock will error when called on a storage engine
// that does not support the begin/end backup commands.
var supportsFsync = db.fsyncLock();

if (!supportsFsync.ok) {
    assert.commandFailedWithCode(supportsFsync, ErrorCodes.CommandNotSupported);
    jsTestLog("Skipping test for " + storageEngine + " as it does not support fsync");
    return;
}
db.fsyncUnlock();

var resFail = fsyncLockDB.runCommand({fsync:1, lock:1});

// Start with a clean DB
var fsyncLockDB = db.getSisterDB('fsyncLockTestDB');
fsyncLockDB.dropDatabase();

// Test it doesn't work unless invoked against the admin DB
var resFail = fsyncLockDB.runCommand({fsync:1, lock:1});
assert(!resFail.ok, "fsyncLock command succeeded against DB other than admin.");

// Uses admin automatically and locks the server for writes
var fsyncLockRes = db.fsyncLock();
assert(fsyncLockRes.ok, "fsyncLock command failed against admin DB");
assert(db.currentOp().fsyncLock, "Value in db.currentOp incorrect for fsyncLocked server");

// Make sure writes are blocked. Spawn a write operation in a separate shell and make sure it
// is blocked. There is really now way to do that currently, so just check that the write didn't
// go through.
var writeOpHandle = startParallelShell("db.getSisterDB('fsyncLockTestDB').coll.insert({x:1});");
sleep(1000);

// Make sure reads can still run even though there is a pending write and also that the write
// didn't get through
assert.eq(0, fsyncLockDB.coll.count({}));

// Unlock and make sure the insert succeeded
var fsyncUnlockRes = db.fsyncUnlock();
assert(fsyncUnlockRes.ok, "fsyncUnlock command failed");
assert(db.currentOp().fsyncLock == null, "fsyncUnlock is not null in db.currentOp");

// Make sure the db is unlocked and the initial write made it through.
writeOpHandle();
fsyncLockDB.coll.insert({x:2});

assert.eq(2, fsyncLockDB.coll.count({}));

// Issue the fsyncLock and fsyncUnlock a second time, to ensure that we can
// run this command repeatedly with no problems.
var fsyncLockRes = db.fsyncLock();
assert(fsyncLockRes.ok, "Second execution of fsyncLock command failed");

var fsyncUnlockRes = db.fsyncUnlock();
assert(fsyncUnlockRes.ok, "Second execution of fsyncUnlock command failed");

// Ensure eval is not allowed to invoke fsyncLock
assert(!db.eval('db.fsyncLock()').ok, "eval('db.fsyncLock()') should fail.");

// Check that the fsyncUnlock pseudo-command (a lookup on cmd.$sys.unlock)
// still has the same effect as a legitimate 'fsyncUnlock' command
// TODO: remove this in in the release following MongoDB 3.2 when pseudo-commands
// are removed
var fsyncCommandRes = db.fsyncLock();
assert(fsyncLockRes.ok, "fsyncLock command failed against admin DB");
assert(db.currentOp().fsyncLock, "Value in db.currentOp incorrect for fsyncLocked server");
var fsyncPseudoCommandRes = db.getSiblingDB("admin").$cmd.sys.unlock.findOne();
assert(fsyncPseudoCommandRes.ok, "fsyncUnlock pseudo-command failed");
assert(db.currentOp().fsyncLock == null, "fsyncUnlock is not null in db.currentOp");
}());
